<template>
  <view class="chat-page">
    <!-- 顶部栏：左侧返回图标 + 中间标题“英语陪聊” + 右侧语音开关 -->
    <view class="page-header">
      <view class="back-btn" @click="goBackToHome">
        <image src="/static/icons/back-arrow.png" class="back-icon" mode="aspectFit" />
      </view>
      <view class="header-title">英语陪聊</view>
      <view class="header-right">
        <button class="global-voice-btn" @click="toggleGlobalVoice">
          <image
              :src="isVoiceGlobalOn ? '/static/icons/speaker-on.png' : '/static/icons/speaker-off.png'"
              class="global-voice-icon"
              mode="widthFix"
          />
        </button>
      </view>
    </view>

    <!-- 聊天容器内容 -->
    <view class="chat-container">
      <!-- 聊天消息列表：自适应高度 + 滚动 -->
      <view class="chat-messages" ref="messageContainer">
        <view
            v-for="(msg, index) in messages"
            :key="msg.id"
            :class="['message-item', { 'user-message': msg.isUser, 'ai-message': !msg.isUser }]"
        >
          <view class="avatar">
            <image :src="msg.isUser ? userAvatar : aiAvatar" mode="widthFix" />
          </view>
          <view class="message-bubble">
            <view class="message-content" v-html="parseMarkdown(msg.content)"></view>
            <view class="message-time">{{ formatTime(msg.timestamp) }}</view>
          </view>
          <!-- AI消息右侧的语音播放按钮 -->
          <button
              v-if="!msg.isUser"
              class="message-voice-btn"
              @click="playMessageVoice(msg.content, msg.id)"
          >
            <view class="voice-play-icon" :class="{ 'playing': isPlayingVoiceId === msg.id }">
              <!-- 未播放：显示“播放”图标 -->
              <svg v-if="isPlayingVoiceId !== msg.id" class="play-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <polygon points="5 3 19 12 5 21 5 3" />
              </svg>
              <!-- 播放中：显示“波形动画” -->
              <view v-else class="audio-wave">
                <span></span><span></span><span></span><span></span><span></span>
              </view>
            </view>
          </button>
        </view>
      </view>

      <!-- 输入区域：固定在底部 + 高可见性 -->
      <view class="chat-input">
        <textarea
            v-model="inputValue"
            class="input-text"
            placeholder="输入消息（支持 Markdown，回车换行，点击发送提交）"
            @keydown.enter="handleEnter"
        ></textarea>
        <button class="send-btn" @click="sendMessage">发送</button>
        <label class="upload-btn">
          上传图片
          <input
              type="file"
              accept="image/*"
              multiple
              @change="handleImageUpload"
              style="display: none;"
          />
        </label>
        <view class="voice-container" :class="{ 'recording': isRecording }">
          <button
              class="voice-btn"
              :disabled="isUploading"
              @touchstart="handleVoiceTouchStart"
              @touchmove="handleVoiceTouchMove"
              @touchend="handleVoiceTouchEnd"
              @touchcancel="handleVoiceTouchCancel"
              @mousedown="handleVoiceMouseDown"
              @mouseup="handleVoiceMouseUp"
              @mousemove="handleGlobalMouseMove"
          >
            <view class="voice-tip" v-if="isRecording">
              <view class="recording-indicator">
                <view class="audio-wave" :class="{ 'animate': isRecording }">
                  <span></span><span></span><span></span><span></span><span></span>
                </view>
              </view>
              <view class="tip-text" :style="{ color: cancelTip ? '#ff4d4f' : '#fff' }">
                {{ cancelTip ? '上滑/离开取消' : '按住录音，松开发送' }}
              </view>
            </view>
            <view v-else>
              <svg
                  class="mic-icon"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
              >
                <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
                <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
                <line x1="12" y1="19" x2="12" y2="23" />
                <line x1="8" y1="23" x2="16" y2="23" />
              </svg>
            </view>
          </button>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted, nextTick, onUnmounted } from 'vue';
import markdownIt from 'markdown-it';
// 请确保 request.js 路径正确，且支持 postRaw 方法（返回 ArrayBuffer）
import request from '@/utils/request.js';

/* ========== 基础响应式变量定义 ========== */
// 聊天消息列表（存储所有聊天记录）
const messages = ref([]);
// 输入框内容
const inputValue = ref('');
// 用户/AI头像路径
const userAvatar = ref('/static/avatar/user.png');
const aiAvatar = ref('/static/avatar/ai.png');
// 消息容器DOM引用（用于滚动到底部）
const messageContainer = ref(null);
// AI回复文本聚合（用于语音播报拼接）
const aiRepliesText = ref('');
// 全局语音开关状态（默认关闭）
const isVoiceGlobalOn = ref(false);
// 当前正在播放的语音消息ID（用于控制播放状态显示）
const isPlayingVoiceId = ref('');
// 音频播放实例（UniApp原生音频上下文，适配安卓）
const innerAudioContext = uni.createInnerAudioContext();

/* ========== 缓存配置常量（安卓专用） ========== */
const CACHE_META_KEY = 'tts_cache_meta'; // 缓存元数据存储Key（路径、过期时间等）
const CACHE_FILE_PREFIX = 'tts_audio_'; // 音频文件缓存Key前缀
const CACHE_EXPIRE = 10 * 24 * 60 * 60 * 1000; // 缓存有效期：10天（毫秒）
const CLEAN_THRESHOLD_5D = 5 * 24 * 60 * 60 * 1000; // 5天过期阈值（用于梯度清理）
const CLEAN_THRESHOLD_15D = 15 * 24 * 60 * 60 * 1000; // 15天过期阈值（优先清理）
const MAX_CACHE_SIZE = 100 * 1024 * 1024; // 缓存最大容量：100MB（字节）

/* ========== 基础工具函数 ========== */
/**
 * 生成唯一消息ID（避免重复）
 */
const generateUniqueId = () => `msg_${Date.now()}_${Math.floor(Math.random() * 10000)}`;

/**
 * Markdown解析（将AI返回的Markdown文本转为HTML）
 */
const md = new markdownIt();
const parseMarkdown = (content) => md.render(content);

/**
 * 时间格式化（转为 HH:MM:SS 格式）
 */
const formatTime = (timestamp) => {
  const date = new Date(timestamp);
  const pad = (num) => num.toString().padStart(2, '0');
  return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
};

/**
 * 滚动消息列表到底部（新消息出现时调用）
 */
const scrollToBottom = () => {
  if (messageContainer.value) {
    nextTick(() => {
      messageContainer.value.scrollTop = messageContainer.value.scrollHeight;
    });
  }
};

/* ========== 缓存核心逻辑（安卓原生文件系统） ========== */
/**
 * 1. 获取缓存元数据（存储所有音频的路径、过期时间、大小）
 */
const getCacheMeta = async () => {
  try {
    const res = await uni.getStorage({ key: CACHE_META_KEY });
    return res.data ? JSON.parse(res.data) : {};
  } catch (error) {
    console.error('【缓存工具】获取元数据失败', error);
    return {};
  }
};

/**
 * 2. 保存缓存元数据到本地存储
 */
const saveCacheMeta = async (meta) => {
  try {
    await uni.setStorage({
      key: CACHE_META_KEY,
      data: JSON.stringify(meta)
    });
    return true;
  } catch (error) {
    console.error('【缓存工具】保存元数据失败', error);
    return false;
  }
};

/**
 * 3. 计算当前缓存总大小（安卓：读取实际文件大小）
 */
const calculateTotalCacheSize = async () => {
  let totalSize = 0;
  const meta = await getCacheMeta();
  const cacheKeys = Object.keys(meta);

  if (cacheKeys.length > 0) {
    try {
      // 获取所有本地保存的文件列表
      const fileListRes = await uni.getSavedFileList();
      for (const file of fileListRes.fileList) {
        // 匹配元数据中的文件路径
        const matchKey = cacheKeys.find(key => meta[key].filePath === file.filePath);
        if (matchKey) {
          // 获取文件详情（包含实际大小）
          const fileInfo = await uni.getSavedFileInfo({ filePath: file.filePath });
          totalSize += fileInfo.size;
          // 更新元数据中的文件大小（防止偏差）
          meta[matchKey].size = fileInfo.size;
        }
      }
      await saveCacheMeta(meta); // 同步更新元数据
    } catch (error) {
      console.warn('【缓存工具】计算实际大小失败，使用元数据估算', error);
      // 降级方案：用元数据中记录的大小估算
      totalSize = cacheKeys.reduce((sum, key) => sum + (meta[key].size || 0), 0);
    }
  }
  return totalSize;
};

/**
 * 4. 梯度清理缓存（优先删15天过期，再删5天过期且超容量的）
 */
const gradientCleanTtsCache = async () => {
  const now = Date.now();
  const meta = await getCacheMeta();
  const cacheKeys = Object.keys(meta);
  const expired15DKeys = []; // 过期15天的缓存Key
  const expired5DKeys = []; // 过期5天但未到15天的缓存Key

  // 第一步：筛选过期缓存Key
  for (const key of cacheKeys) {
    const expireTime = now - meta[key].expireAt;
    if (expireTime > CLEAN_THRESHOLD_15D) {
      expired15DKeys.push(key);
    } else if (expireTime > CLEAN_THRESHOLD_5D) {
      expired5DKeys.push(key);
    }
  }

  // 第二步：优先删除过期15天的文件和元数据
  for (const key of expired15DKeys) {
    try {
      await uni.removeSavedFile({ filePath: meta[key].filePath });
      console.log(`【缓存清理】删除15天过期文件：${meta[key].filePath}`);
    } catch (error) {
      console.error(`【缓存清理】删除文件失败：${meta[key].filePath}`, error);
    }
    delete meta[key]; // 移除无效元数据
  }

  // 第三步：检查总容量，超100M则删除最早过期的5天缓存
  const totalSize = await calculateTotalCacheSize();
  if (totalSize > MAX_CACHE_SIZE && expired5DKeys.length > 0) {
    // 按过期时间排序（最早过期的先删）
    expired5DKeys.sort((a, b) => meta[a].expireAt - meta[b].expireAt);

    let deletedSize = 0;
    for (const key of expired5DKeys) {
      try {
        await uni.removeSavedFile({ filePath: meta[key].filePath });
        deletedSize += meta[key].size || 0;
        console.log(`【缓存清理】超容量删除5天过期文件：${meta[key].filePath}`);
        delete meta[key];

        // 达到容量限制则停止删除
        if (totalSize - deletedSize <= MAX_CACHE_SIZE) break;
      } catch (error) {
        console.error(`【缓存清理】删除文件失败：${meta[key].filePath}`, error);
      }
    }
  } else {
    console.log(`【缓存清理】当前容量：${(totalSize / 1024 / 1024).toFixed(2)}MB（≤100MB），无需清理`);
  }

  // 第四步：同步更新元数据到本地存储
  await saveCacheMeta(meta);
};

/* ========== WebSocket 消息通信 ========== */
// 初始化WebSocket连接（请替换为实际后端地址）
const socket = new WebSocket('ws://192.168.3.31:8080/agent/chat/say');

/**
 * 监听WebSocket消息（接收AI回复）
 */
socket.addEventListener('message', async (event) => {
  try {
    const data = JSON.parse(event.data);
    const { content, role, conversationId } = data;

    // 只处理AI（assistant）的消息
    if (role !== 'assistant' || !content) return;

    // 查找是否已有该对话的AI消息（用于流式回复拼接）
    let aiMessage = messages.value.find(msg => !msg.isUser && msg.conversationId === conversationId);
    if (!aiMessage) {
      // 新AI消息：添加到消息列表
      aiMessage = {
        id: generateUniqueId(),
        content,
        isUser: false,
        timestamp: Date.now(),
        conversationId
      };
      messages.value.push(aiMessage);
    } else {
      // 流式回复：拼接内容
      aiMessage.content += content;
      messages.value = [...messages.value]; // 触发视图更新
    }

    // 聚合AI回复文本（用于语音播报）
    aiRepliesText.value += content;
    await nextTick();
    scrollToBottom(); // 滚动到最新消息

    // 全局语音开启时，自动播放当前AI消息
    if (isVoiceGlobalOn.value) {
      setTimeout(() => {
        playMessageVoice(content, aiMessage.id);
      }, 100);
    }
  } catch (error) {
    // 消息解析失败：添加错误提示
    messages.value.push({
      id: generateUniqueId(),
      content: '消息解析失败，请重试',
      isUser: false,
      timestamp: Date.now()
    });
    await nextTick();
    scrollToBottom();
  }
});

/* ========== 文本消息功能 ========== */
/**
 * 处理回车换行（Shift+Enter换行，单独Enter提交）
 */
const handleEnter = (e) => {
  if (e.shiftKey) return; // Shift+Enter允许换行
  e.preventDefault(); // 阻止默认提交行为
  const start = e.target.selectionStart;
  const end = e.target.selectionEnd;
  // 在光标位置插入换行符
  inputValue.value = `${inputValue.value.slice(0, start)}\n${inputValue.value.slice(end)}`;
  // 重置光标位置到换行后
  nextTick(() => {
    e.target.selectionStart = e.target.selectionEnd = start + 1;
  });
};

/**
 * 发送文本消息
 */
const sendMessage = () => {
  const content = inputValue.value.trim();
  if (!content) return; // 空消息不发送

  // 生成用户消息并添加到列表
  const messageId = generateUniqueId();
  const conversationId = generateUniqueId();
  const userMsg = {
    id: messageId,
    content,
    isUser: true,
    timestamp: Date.now(),
    conversationId
  };
  messages.value.push(userMsg);
  inputValue.value = ''; // 清空输入框

  // 发送消息到WebSocket后端
  socket.send(JSON.stringify({
    content,
    role: 'user',
    conversationId
  }));

  scrollToBottom(); // 滚动到最新消息
};

/* ========== 图片上传功能 ========== */
/**
 * 处理图片上传（通过input[type=file]触发）
 */
const handleImageUpload = async (e) => {
  const files = e.target.files;
  if (!files.length) return; // 无文件则返回

  // 构建FormData（用于文件上传）
  const formData = new FormData();
  files.forEach(file => formData.append('files', file));

  try {
    // 调用图片上传接口
    const res = await request.post('agent/file/upload', formData, {
      'Content-Type': 'multipart/form-data'
    });

    // 【核心修改】判断业务状态码
    if (res.code === 200) {
      const imageUrls = res.data;
      // 添加上传成功消息
      messages.value.push({
        id: generateUniqueId(),
        content: `已上传图片：<br>${imageUrls.map(url => `<img src="${url}" style="max-width:150px;border-radius:8px;margin-top:4px;" />`).join('')}`,
        isUser: true,
        timestamp: Date.now()
      });
    } else {
      // 业务错误，显示后端返回的错误信息
      messages.value.push({
        id: generateUniqueId(),
        content: `图片上传失败：${res.msg || '服务器异常'}`,
        isUser: true,
        timestamp: Date.now()
      });
    }
  } catch (error) {
    // 网络或系统级错误
    messages.value.push({
      id: generateUniqueId(),
      content: '图片上传失败：网络异常，请重试',
      isUser: true,
      timestamp: Date.now()
    });
  } finally {
    scrollToBottom(); // 滚动到最新消息
    // 重置input值（避免重复上传同一文件）
    e.target.value = '';
  }
};

/* ========== 语音录音与上传功能 ========== */
// 录音相关响应式变量
const isRecording = ref(false); // 是否正在录音
const isUploading = ref(false); // 是否正在上传录音
const mediaRecorder = ref(null); // 浏览器录音实例
const audioChunks = ref([]); // 录音数据片段
const cancelTip = ref(false); // 是否显示“取消录音”提示
const touchStartY = ref(0); // 触摸开始时的Y坐标（用于判断上滑取消）
const mouseStartPos = ref({ x: 0, y: 0 }); // 鼠标按下时的坐标（PC端）
const cancelThreshold = ref(30); // 取消录音的阈值（像素）
const streamRef = ref(null); // 音频流引用（用于停止录音）
const timeoutTimerRef = ref(null); // 录音超时计时器（最长10秒）
const recordingPromise = ref(null); // 录音完成的Promise
const uploadMsgId = ref(''); // 上传中消息的ID

/**
 * 清理全局鼠标事件监听（PC端）
 */
const cleanupGlobalMouseEvents = () => {
  document.removeEventListener('mousemove', handleGlobalMouseMove);
  document.removeEventListener('mouseup', handleVoiceMouseUp);
};

/**
 * PC端：鼠标按下开始录音
 */
const handleVoiceMouseDown = (e) => {
  if (e.button !== 0 || isRecording.value || isUploading.value) return;
  mouseStartPos.value = { x: e.clientX, y: e.clientY };
  startVoiceRecording();
  // 绑定全局鼠标事件（监听移动和松开）
  document.addEventListener('mousemove', handleGlobalMouseMove);
  document.addEventListener('mouseup', handleVoiceMouseUp);
};

/**
 * PC端：监听鼠标移动（判断是否取消录音）
 */
const handleGlobalMouseMove = (e) => {
  if (!isRecording.value) return;
  const diffX = Math.abs(e.clientX - mouseStartPos.value.x);
  const diffY = Math.abs(e.clientY - mouseStartPos.value.y);
  // 超过阈值则显示取消提示
  cancelTip.value = diffX > cancelThreshold.value || diffY > cancelThreshold.value;
};

/**
 * PC端：鼠标松开结束录音
 */
const handleVoiceMouseUp = (e) => {
  if (!isRecording.value) {
    cleanupGlobalMouseEvents();
    return;
  }
  const diffX = Math.abs(e.clientX - mouseStartPos.value.x);
  const diffY = Math.abs(e.clientY - mouseStartPos.value.y);
  // 判断是否取消录音
  const shouldCancel = diffX > cancelThreshold.value || diffY > cancelThreshold.value;
  shouldCancel ? cancelVoiceRecord() : handleVoiceRelease();
  cleanupGlobalMouseEvents();
};

/**
 * 移动端：触摸开始录音
 */
const handleVoiceTouchStart = (e) => {
  if (isRecording.value || isUploading.value) return;
  touchStartY.value = e.touches[0].clientY;
  startVoiceRecording();
};

/**
 * 移动端：触摸移动（判断是否上滑取消）
 */
const handleVoiceTouchMove = (e) => {
  if (!isRecording.value) return;
  const diffY = touchStartY.value - e.touches[0].clientY;
  // 上滑超过阈值显示取消提示
  cancelTip.value = diffY > cancelThreshold.value;
};

/**
 * 移动端：触摸结束录音
 */
const handleVoiceTouchEnd = (e) => {
  if (!isRecording.value) return;
  const diffY = touchStartY.value - e.changedTouches[0].clientY;
  const shouldCancel = diffY > cancelThreshold.value;
  shouldCancel ? cancelVoiceRecord() : handleVoiceRelease();
};

/**
 * 移动端：触摸取消（如来电、弹窗等）
 */
const handleVoiceTouchCancel = () => {
  if (isRecording.value) cancelVoiceRecord();
};

/**
 * 开始录音（浏览器API + 适配OPUS格式）
 */
const startVoiceRecording = () => {
  isRecording.value = true;
  cancelTip.value = false;
  audioChunks.value = [];
  // 录音超时控制（最长10秒）
  timeoutTimerRef.value = setTimeout(() => handleVoiceRelease(), 10000);

  // 调用浏览器麦克风权限
  navigator.mediaDevices.getUserMedia({
    audio: {
      sampleRate: 16000, // 16kHz采样率（适配后端识别）
      channelCount: 1, // 单声道
      echoCancellation: false, // 关闭回声消除
      noiseSuppression: false, // 关闭噪声抑制
      autoGainControl: false // 关闭自动增益
    }
  })
      .then((stream) => {
        streamRef.value = stream;
        // 尝试支持的录音格式（优先OPUS）
        const tryFormats = ['audio/opus;codecs=opus', 'audio/webm;codecs=opus', 'audio/opus'];
        let formatIndex = 0;

        /**
         * 递归尝试录音格式（浏览器兼容性处理）
         */
        const tryNextFormat = () => new Promise((resolve, reject) => {
          if (formatIndex >= tryFormats.length) {
            reject(new Error('浏览器不支持OPUS录音格式'));
            return;
          }
          const mimeType = tryFormats[formatIndex++];
          try {
            const recorder = new MediaRecorder(stream, { mimeType });
            resolve(recorder);
          } catch (err) {
            tryNextFormat().then(resolve).catch(reject);
          }
        });

        // 初始化录音实例
        tryNextFormat()
            .then((recorder) => {
              mediaRecorder.value = recorder;
              // 收集录音数据片段
              recorder.ondataavailable = (event) => {
                if (event.data.size > 0) audioChunks.value.push(event.data);
              };
              // 录音完成的Promise（用于后续处理）
              recordingPromise.value = new Promise((resolve) => {
                recorder.onstop = () => {
                  const blob = audioChunks.value.length > 0
                      ? new Blob(audioChunks.value, { type: recorder.mimeType })
                      : null;
                  resolve(blob);
                };
              });
              recorder.start(100); // 每100ms生成一个数据片段
            })
            .catch((err) => {
              // 录音初始化失败
              streamRef.value?.getTracks().forEach(track => track.stop());
              resetRecordingState();
              messages.value.push({
                id: generateUniqueId(),
                content: `🎤 录音初始化失败：${err.message}`,
                isUser: true,
                timestamp: Date.now()
              });
              scrollToBottom();
            });
      })
      .catch((err) => {
        // 麦克风权限获取失败
        resetRecordingState();
        messages.value.push({
          id: generateUniqueId(),
          content: '🎤 无法访问麦克风，请检查权限',
          isUser: true,
          timestamp: Date.now()
        });
        scrollToBottom();
      });
};

/**
 * 结束录音并上传（正常流程）
 */
const handleVoiceRelease = async () => {
  if (!isRecording.value || isUploading.value) {
    resetRecordingState();
    return;
  }
  isRecording.value = false;
  isUploading.value = true;
  clearTimeout(timeoutTimerRef.value); // 清除超时计时器

  // 添加“上传中”消息
  uploadMsgId.value = generateUniqueId();
  messages.value.push({
    id: uploadMsgId.value,
    content: '🎤 录音上传中...',
    isUser: true,
    timestamp: Date.now()
  });
  scrollToBottom();

  try {
    if (!mediaRecorder.value) throw new Error('录音实例不存在');
    mediaRecorder.value.stop(); // 停止录音
    // 等待录音数据生成（超时5秒）
    const audioBlob = await Promise.race([
      recordingPromise.value,
      new Promise((_, reject) => setTimeout(() => reject(new Error('录音数据生成超时')), 5000))
    ]);

    // 停止音频流
    streamRef.value?.getTracks().forEach(track => track.stop());

    if (audioBlob && audioBlob.size > 0) {
      await uploadVoiceToBackend(audioBlob); // 上传录音到后端
    } else {
      updateUploadMsg('🎤 录音数据为空，请重试', false);
    }
  } catch (err) {
    updateUploadMsg(`🎤 录音处理失败：${err.message}`, false);
  } finally {
    resetRecordingState(); // 重置录音状态
    isUploading.value = false;
  }
};

/**
 * 取消录音
 */
const cancelVoiceRecord = async () => {
  if (!isRecording.value || isUploading.value) return;
  clearTimeout(timeoutTimerRef.value); // 清除超时计时器

  // 停止录音
  if (mediaRecorder.value) {
    mediaRecorder.value.stop();
    await recordingPromise.value; // 等待录音停止完成
  }

  // 停止音频流
  streamRef.value?.getTracks().forEach(track => track.stop());

  // 添加“取消录音”消息
  messages.value.push({
    id: generateUniqueId(),
    content: '🎤 录音已取消',
    isUser: true,
    timestamp: Date.now()
  });
  scrollToBottom();
  resetRecordingState(); // 重置录音状态
};

/**
 * 重置录音相关状态
 */
const resetRecordingState = () => {
  isRecording.value = false;
  audioChunks.value = [];
  touchStartY.value = 0;
  mouseStartPos.value = { x: 0, y: 0 };
  mediaRecorder.value = null;
  streamRef.value = null;
  timeoutTimerRef.value = null;
  recordingPromise.value = null;
  uploadMsgId.value = '';
};

/**
 * 更新录音上传结果消息
 */
const updateUploadMsg = (content, isSuccess) => {
  const msgIndex = messages.value.findIndex(msg => msg.id === uploadMsgId.value);
  if (msgIndex !== -1) {
    // 更新已存在的“上传中”消息
    messages.value[msgIndex].content = content;
    messages.value = [...messages.value];
  } else if (!isSuccess) {
    // 上传失败：添加新消息
    messages.value.push({
      id: generateUniqueId(),
      content: content,
      isUser: true,
      timestamp: Date.now()
    });
  }
  scrollToBottom();
};

/**
 * 上传录音到后端（语音转文字）
 */
const uploadVoiceToBackend = async (audioBlob) => {
  // 检查录音大小（限制1MB，约1分钟）
  if (audioBlob.size > 1024 * 1024) {
    const sizeKB = (audioBlob.size / 1024).toFixed(1);
    updateUploadMsg(`🎤 录音过大（${sizeKB}KB），请控制在1分钟内`, false);
    return;
  }

  // 检查录音时长（至少0.5秒）
  const audio = new Audio();
  const duration = await new Promise((resolve) => {
    audio.onloadedmetadata = () => resolve(audio.duration);
    audio.onerror = () => resolve(0);
    audio.src = URL.createObjectURL(audioBlob);
  });
  URL.revokeObjectURL(audio.src); // 释放临时URL

  if (duration < 0.5) {
    updateUploadMsg(`🎤 录音时长过短（${duration.toFixed(1)}秒），请录1秒以上`, false);
    return;
  }

  // 构建FormData（用于文件上传）
  const formData = new FormData();
  formData.append('file', audioBlob, 'recording.opus'); // 录音文件
  formData.append('sampleRate', '16000'); // 采样率
  formData.append('audioType', 'opus'); // 音频格式

  try {
    // 调用语音转文字接口
    const res = await request.post('agent/voice/voiceToText', formData, {
      'Content-Type': 'multipart/form-data',
      timeout: 30000 // 超时30秒
    });

    // 【核心修改】判断业务状态码
    if (res.code === 200) {
      const text = res.data;
      // 上传成功：显示识别结果并发送消息
      updateUploadMsg(`🎤 语音识别结果：${text}`, true);
      sendMessageViaWebSocket(text);
    } else {
      // 业务错误，显示后端返回的错误信息
      updateUploadMsg(`🎤 语音识别失败：${res.msg || '识别服务异常'}`, false);
    }
  } catch (error) {
    // 网络或系统级错误
    updateUploadMsg(`🎤 语音识别失败：${error.message || '网络异常'}`, false);
  }
};

/**
 * 通过WebSocket发送语音识别后的文本消息
 */
const sendMessageViaWebSocket = (text) => {
  const conversationId = generateUniqueId();
  socket.send(JSON.stringify({
    content: text,
    role: 'user',
    conversationId
  }));
  scrollToBottom();
};

/* ========== 文本转语音功能（带缓存） ========== */
/**
 * 生成缓存Key（处理特殊字符，避免重复）
 */
const getCacheKey = (text) => {
  return `${CACHE_FILE_PREFIX}${encodeURIComponent(text)}`;
};

/**
 * 文本转语音（优先读缓存，无缓存则调用接口并保存）
 */
const convertTextToVoice = async (text) => {
  const cacheKey = getCacheKey(text);
  const meta = await getCacheMeta();

  // 第一步：检查缓存是否命中且未过期
  if (meta[cacheKey]) {
    const { filePath, expireAt } = meta[cacheKey];
    if (Date.now() < expireAt) {
      try {
        // 验证文件是否存在（防止被手动删除）
        await uni.getSavedFileInfo({ filePath });
        console.log(`[TTS缓存] 命中缓存：${filePath}`);
        return filePath; // 返回文件路径（用于播放）
      } catch (error) {
        console.warn(`[TTS缓存] 文件不存在，清理无效元数据：${filePath}`, error);
        delete meta[cacheKey];
        await saveCacheMeta(meta);
      }
    } else {
      // 缓存过期：删除文件和元数据
      await uni.removeSavedFile({ filePath }).catch(err => console.warn('删除过期文件失败', err));
      delete meta[cacheKey];
      await saveCacheMeta(meta);
    }
  }

  // 第二步：无缓存/过期，调用文本转语音接口
  try {
    // 调用接口获取音频二进制流（responseType: arraybuffer）
    const audioArrayBuffer = await request.postRaw(
        'agent/voice/textToVoice',
        { text },
        { 'Content-Type': 'application/json' },
        { responseType: 'arraybuffer' }
    );

    // 验证接口返回是否为有效二进制流
    if (!(audioArrayBuffer instanceof ArrayBuffer)) {
      throw new Error('接口返回非二进制流数据');
    }

    // 第三步：将二进制流转为Blob并保存到本地
    const audioBlob = new Blob([audioArrayBuffer], { type: 'audio/mpeg' });
    const tempFilePath = URL.createObjectURL(audioBlob); // 生成临时URL

    // 保存文件到安卓本地存储（UniApp原生API）
    const saveRes = await uni.saveFile({ tempFilePath });
    if (saveRes.errMsg !== 'saveFile:ok') {
      throw new Error(`文件保存失败：${saveRes.errMsg}`);
    }

    // 第四步：更新缓存元数据
    const fileInfo = await uni.getSavedFileInfo({ filePath: saveRes.savedFilePath });
    meta[cacheKey] = {
      filePath: saveRes.savedFilePath,
      expireAt: Date.now() + CACHE_EXPIRE,
      size: fileInfo.size,
      text: text.slice(0, 50) // 存储简短文本（用于调试）
    };
    await saveCacheMeta(meta);
    console.log(`[TTS缓存] 保存文件成功：${saveRes.savedFilePath}`);

    return saveRes.savedFilePath; // 返回文件路径（用于播放）
  } catch (error) {
    console.error('[TTS缓存] 处理失败', error);
    return null;
  }
};

/**
 * 播放音频文件（基于本地文件路径）
 */
const playAudio = async (filePath) => {
  return new Promise((resolve) => {
    // 停止当前正在播放的音频
    if (!innerAudioContext.paused) {
      innerAudioContext.stop();
    }

    // 配置音频源并播放
    innerAudioContext.src = filePath;
    innerAudioContext.autoplay = true; // 自动播放

    // 播放结束回调
    innerAudioContext.onEnded(() => {
      console.log('[音频播放] 播放完成');
      innerAudioContext.offEnded(); // 移除监听
      innerAudioContext.offError();
      resolve();
    });

    // 播放错误回调
    innerAudioContext.onError((res) => {
      console.error('[音频播放] 失败', res.errMsg, res.errCode);
      uni.showToast({ title: '语音播放失败', icon: 'none' });
      innerAudioContext.offEnded();
      innerAudioContext.offError();
      resolve();
    });

    // 触发播放（处理浏览器自动播放限制）
    innerAudioContext.play().catch(err => {
      console.error('[音频播放] 触发失败', err);
      uni.showToast({ title: '语音播放失败', icon: 'none' });
      resolve();
    });
  });
};

/**
 * 单条AI消息语音播放（控制播放状态）
 */
const playMessageVoice = async (text, msgId) => {
  // 点击正在播放的消息：停止播放
  if (isPlayingVoiceId.value === msgId) {
    innerAudioContext.stop();
    isPlayingVoiceId.value = '';
    return;
  }

  try {
    isPlayingVoiceId.value = msgId; // 标记当前播放的消息ID
    const audioFilePath = await convertTextToVoice(text); // 获取音频路径
    if (audioFilePath) {
      await playAudio(audioFilePath); // 播放音频
    } else {
      uni.showToast({ title: '语音资源获取失败', icon: 'none' });
    }
  } catch (error) {
    console.error('[消息语音播放] 整体失败', error);
    uni.showToast({ title: '语音播放失败', icon: 'none' });
  } finally {
    isPlayingVoiceId.value = ''; // 重置播放状态
  }
};

/**
 * 全局语音开关切换
 */
const toggleGlobalVoice = () => {
  isVoiceGlobalOn.value = !isVoiceGlobalOn.value;
  // 显示开关状态提示
  uni.showToast({
    title: isVoiceGlobalOn.value ? '已开启自动语音播报' : '已关闭自动语音播报',
    icon: 'none',
    duration: 1000
  });
};

/* ========== 组件生命周期 ========== */
/**
 * 组件挂载时初始化
 */
onMounted(() => {
  // 1. 初始化缓存清理（清理过期缓存）
  gradientCleanTtsCache().catch(err => console.error('缓存清理初始化失败', err));
  // 2. 滚动消息列表到底部
  scrollToBottom();

  // 页面卸载时销毁音频实例（避免内存泄漏）
  onUnmounted(() => {
    innerAudioContext.destroy();
  });
});

/**
 * 组件卸载时清理资源
 */
onUnmounted(() => {
  // 1. 停止录音音频流（防止麦克风占用）
  if (streamRef.value) {
    streamRef.value.getTracks().forEach(track => track.stop());
  }
  // 2. 关闭WebSocket连接
  socket.close();
  // 3. 清理全局鼠标事件监听（PC端）
  cleanupGlobalMouseEvents();
});

/**
 * 返回首页
 */
const goBackToHome = () => {
  uni.navigateTo({ url: '/pages/home/index' });
};
</script>

<style scoped>
/* 全局盒模型重置 */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* 页面主容器 */
.chat-page {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #f5f7fa;
  overflow: hidden; /* 禁止页面整体滚动 */
}

/* 顶部导航栏 */
.page-header {
  display: flex;
  align-items: center;
  height: 44px;
  padding: 0 12px;
  background-color: #fff;
  border-bottom: 1px solid #eee;
  z-index: 10; /* 确保顶部栏在最上层 */
}

.back-btn {
  margin-right: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.back-icon {
  width: 24px;
  height: 24px;
}

.header-title {
  flex: 1;
  text-align: center;
  font-size: 18px;
  font-weight: bold;
  color: #333;
}

.global-voice-btn {
  background: transparent;
  border: none;
  padding: 4px;
  cursor: pointer;
}

.global-voice-icon {
  width: 20px;
  height: 20px;
}

/* 聊天容器（消息列表 + 输入区） */
.chat-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}

/* 消息列表容器（可滚动） */
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px; /* 消息之间的间距 */
}

/* 单个消息项 */
.message-item {
  display: flex;
  max-width: 70%; /* 消息最大宽度（避免过宽） */
  align-items: flex-start;
}

/* 用户消息（靠右） */
.user-message {
  margin-left: auto;
  flex-direction: row-reverse;
}

/* AI消息（靠左） */
.ai-message {
  margin-right: auto;
  flex-direction: row;
}

/* 头像样式 */
.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  overflow: hidden;
  margin: 0 6px;
  align-self: flex-start;
  flex-shrink: 0; /* 防止头像被压缩 */
}

.avatar image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 消息气泡 */
.message-bubble {
  display: flex;
  flex-direction: column;
  padding: 6px 10px;
  border-radius: 12px;
  max-width: calc(100% - 48px); /* 适配头像宽度 */
  word-wrap: break-word; /* 长文本自动换行 */
}

/* 用户消息气泡样式 */
.user-message .message-bubble {
  background-color: #d3eafd;
  border: 1px solid #b3d8f4;
}

/* AI消息气泡样式 */
.ai-message .message-bubble {
  background-color: #f1f1f1;
  border: 1px solid #e5e5e5;
}

/* 消息内容 */
.message-content {
  font-size: 14px;
  line-height: 1.5;
  color: #333;
  margin-bottom: 4px;
}

/* 消息时间 */
.message-time {
  font-size: 11px;
  color: #999;
  text-align: right;
}

/* 语音播放按钮（AI消息右侧） */
.message-voice-btn {
  background: transparent;
  border: none;
  padding: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  margin-top: auto;
  margin-left: 4px;
}

/* 播放图标（未播放状态） */
.play-icon {
  width: 20px;
  height: 20px;
  color: #666;
}

/* 波形容器（播放中状态） */
.audio-wave {
  display: flex;
  align-items: flex-end;
  height: 20px;
  gap: 2px; /* 波形条之间的间距 */
}

/* 波形条样式 + 动画 */
.audio-wave span {
  width: 2px;
  background-color: #409eff;
  border-radius: 2px;
  animation: voiceWave 1s ease-in-out infinite;
}

/* 波形动画延迟（形成错落效果） */
.audio-wave span:nth-child(1) { animation-delay: 0s; }
.audio-wave span:nth-child(2) { animation-delay: 0.1s; }
.audio-wave span:nth-child(3) { animation-delay: 0.2s; }
.audio-wave span:nth-child(4) { animation-delay: 0.3s; }
.audio-wave span:nth-child(5) { animation-delay: 0.4s; }

/* 波形动画关键帧（高低起伏） */
@keyframes voiceWave {
  0%, 100% { height: 4px; }
  50% { height: 16px; }
}

/* 播放中：波形颜色强调 */
.voice-play-icon.playing .audio-wave span {
  background-color: #1989fa;
}

/* 底部输入区域 */
.chat-input {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: #fff;
  border-top: 1px solid #e5e5e5;
  height: 56px;
  z-index: 10;
}

/* 输入框 */
.input-text {
  flex: 1;
  height: 36px;
  padding: 0 12px;
  border: 1px solid #ddd;
  border-radius: 18px;
  resize: none;
  font-size: 14px;
  color: #333;
  line-height: 36px; /* 文本垂直居中 */
}

/* 发送按钮 & 上传按钮 */
.send-btn, .upload-btn {
  height: 36px;
  padding: 0 14px;
  border: none;
  border-radius: 18px;
  color: #fff;
  cursor: pointer;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.2s;
}

.send-btn {
  background: #409eff;
}

.send-btn:hover {
  background: #66b1ff;
}

.upload-btn {
  background: #67c23a;
}

.upload-btn:hover {
  background: #85ce61;
}

/* 录音按钮容器 */
.voice-container {
  position: relative;
}

/* 录音按钮 */
.voice-btn {
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 50%;
  background: #409eff;
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.2s;
}

.voice-btn:hover {
  background: #66b1ff;
}

/* 录音中按钮样式 */
.voice-container.recording .voice-btn {
  background: #ff4d4f;
}

/* 录音提示框（录音时显示） */
.voice-tip {
  position: absolute;
  bottom: 120%;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.9);
  color: #fff;
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 11px;
  white-space: nowrap;
  z-index: 20;
}

/* 录音中波形指示器 */
.recording-indicator .audio-wave {
  margin: 0 auto 4px;
}

.recording-indicator .audio-wave span {
  background-color: #fff;
}

/* 录音提示文本 */
.tip-text {
  font-size: 12px;
}

/* 麦克风图标（未录音状态） */
.mic-icon {
  width: 18px;
  height: 18px;
}
</style>